/*
 *  linux/fs/buffer.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  'buffer.c' implements the buffer-cache functions. Race-conditions have
 * been avoided by NEVER letting a interrupt change a buffer (except for the
 * data, of course), but instead letting the caller do it. NOTE! As interrupts
 * can wake up a caller, some cli-sti sequences are needed to check for
 * sleep-on-calls. These should be extremely quick, though (I hope).
 */

/*
 * NOTE! There is one discordant note here: checking floppies for
 * disk change. This is where it fits best, I think, as it should
 * invalidate changed floppy-disk-caches.
 */

#include <stdarg.h>

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/io.h>

extern int end;
struct buffer_head * start_buffer = (struct buffer_head *) &end;
struct buffer_head * hash_table[NR_HASH];
static struct buffer_head * free_list;
static struct task_struct * buffer_wait = NULL;
int NR_BUFFERS = 0;

/************************ semaphore variable ******************************/
unsigned long block_query_semaphore = 0;
//unsigned long block_read_semaphore = 0;
unsigned long block_count_semaphore = 0;
/**************************************************************************/

static inline void wait_on_buffer(struct buffer_head * bh) {
	cli();
	while (bh->b_lock) {
		sleep_on(&bh->b_wait);
	}
	sti();
}

int sys_sync(void) {
	int i;
	struct buffer_head * bh;

	sync_inodes(); /* write out inodes into buffers */
	bh = start_buffer;
	for (i = 0; i < NR_BUFFERS; i++, bh++) {
		wait_on_buffer(bh);
		if (bh->b_dirt)
			ll_rw_block(WRITE, bh);
	}
	return 0;
}

int sync_dev(int dev) {
	int i;
	struct buffer_head * bh;

	bh = start_buffer;
	for (i = 0; i < NR_BUFFERS; i++, bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE, bh);
	}
	sync_inodes();
	bh = start_buffer;
	for (i = 0; i < NR_BUFFERS; i++, bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev && bh->b_dirt)
			ll_rw_block(WRITE, bh);
	}
	return 0;
}

void invalidate_buffers(int dev) {
	int i;
	struct buffer_head * bh;

	bh = start_buffer;
	for (i = 0; i < NR_BUFFERS; i++, bh++) {
		if (bh->b_dev != dev)
			continue;
		wait_on_buffer(bh);
		if (bh->b_dev == dev)
			bh->b_uptodate = bh->b_dirt = 0;
	}
}

/*
 * This routine checks whether a floppy has been changed, and
 * invalidates all buffer-cache-entries in that case. This
 * is a relatively slow routine, so we have to try to minimize using
 * it. Thus it is called only upon a 'mount' or 'open'. This
 * is the best way of combining speed and utility, I think.
 * People changing diskettes in the middle of an operation deserve
 * to loose :-)
 *
 * NOTE! Although currently this is only for floppies, the idea is
 * that any additional removable block-device will use this routine,
 * and that mount/open needn't know that floppies/whatever are
 * special.
 */
void check_disk_change(int dev) {
	int i;

	if (MAJOR(dev) != 2)
		return;
	if (!floppy_change(dev & 0x03))
		return;
	for (i = 0; i < NR_SUPER; i++)
		if (super_block[i].s_dev == dev)
			put_super(super_block[i].s_dev);
	invalidate_inodes(dev);
	invalidate_buffers(dev);
}

#define _hashfn(dev,block) (((unsigned)(dev^block))%NR_HASH)
#define hash(dev,block) hash_table[_hashfn(dev,block)]

static inline void remove_from_queues(struct buffer_head * bh) {
	/* remove from hash-queue */
	if (bh->b_next)
		bh->b_next->b_prev = bh->b_prev;
	if (bh->b_prev)
		bh->b_prev->b_next = bh->b_next;
	if (hash(bh->b_dev,bh->b_blocknr)== bh)
	hash(bh->b_dev,bh->b_blocknr) = bh->b_next;
	/* remove from free list */
	if (!(bh->b_prev_free) || !(bh->b_next_free))
		panic("Free block list corrupted");
	bh->b_prev_free->b_next_free = bh->b_next_free;
	bh->b_next_free->b_prev_free = bh->b_prev_free;
	if (free_list == bh)
		free_list = bh->b_next_free;
}

static inline void insert_into_queues(struct buffer_head * bh) {
	/* put at end of free list */
	bh->b_next_free = free_list;
	bh->b_prev_free = free_list->b_prev_free;
	free_list->b_prev_free->b_next_free = bh;
	free_list->b_prev_free = bh;
	/* put the buffer in new hash-queue if it has a device */
	bh->b_prev = NULL;
	bh->b_next = NULL;
	if (!bh->b_dev)
		return;
	bh->b_next = hash(bh->b_dev, bh->b_blocknr);
	hash(bh->b_dev,bh->b_blocknr)= bh;
    if (bh->b_next) bh->b_next->b_prev = bh;
}

static struct buffer_head * find_buffer(int dev, int block) {
	struct buffer_head * tmp;

	for (tmp = hash(dev, block); tmp != NULL; tmp = tmp->b_next) {
		if (tmp->b_dev == dev && tmp->b_blocknr == block)
			return tmp;
	}
	return NULL;
}

/*
 * Why like this, I hear you say... The reason is race-conditions.
 * As we don't lock buffers (unless we are readint them, that is),
 * something might happen to it while we sleep (ie a read-error
 * will force it bad). This shouldn't really happen currently, but
 * the code is ready.
 */
struct buffer_head * get_hash_table(int dev, int block) {
	struct buffer_head * bh;

	for (;;) {

		if (!(bh = find_buffer(dev, block))) {
			return NULL;
		}
        /* 并发访问的时候,一定要加同步 */
		lock_op(&block_count_semaphore);
		bh->b_count++;
		unlock_op(&block_count_semaphore);

		wait_on_buffer(bh);

		if (bh->b_dev == dev && bh->b_blocknr == block)
			return bh;

		lock_op(&block_count_semaphore);
		bh->b_count--;
		unlock_op(&block_count_semaphore);
	}
}

/*
 * Ok, this is getblk, and it isn't very clear, again to hinder
 * race-conditions. Most of the code is seldom used, (ie repeating),
 * so it should be much more efficient than it looks.
 *
 * The algoritm is changed: hopefully better, and an elusive bug removed.
 */
#define BADNESS(bh) (((bh)->b_dirt<<1)+(bh)->b_lock)
struct buffer_head * getblk(int dev, int block) {
	struct buffer_head * tmp, *bh;

	repeat:
	if ((bh = get_hash_table(dev, block))) {
		return bh;
	}

	tmp = free_list;
	do {
		if (tmp->b_count)
			continue;
		if (!bh || BADNESS(tmp) < BADNESS(bh)) {
			bh = tmp;
			if (!BADNESS(tmp))
				break;
		}
		/* and repeat until we find something good */
	} while ((tmp = tmp->b_next_free) != free_list);
	if (!bh) {
		sleep_on(&buffer_wait);
		goto repeat;
	}
	wait_on_buffer(bh);
	if (bh->b_count)
		goto repeat;

	while (bh->b_dirt) {
		sync_dev(bh->b_dev);
		wait_on_buffer(bh);
		if (bh->b_count)
			goto repeat;
	}
	/* NOTE!! While we slept waiting for this block, somebody else might */
	/* already have added "this" block to the cache. check it */
	if (find_buffer(dev, block))
		goto repeat;
	/* OK, FINALLY we know that this buffer is the only one of it's kind, */
	/* and that it's unused (b_count=0), unlocked (b_lock=0), and clean */

	lock_op(&block_query_semaphore);
	if (bh->b_count) {
		unlock_op(&block_query_semaphore);
		goto repeat;
	}

	bh->b_count = 1;
	bh->b_dirt = 0;
	bh->b_uptodate = 0;
	remove_from_queues(bh);
	bh->b_dev = dev;
	bh->b_blocknr = block;
	insert_into_queues(bh);
	unlock_op(&block_query_semaphore);
	return bh;
}

unsigned long brelse_semaphore = 0;
void brelse(struct buffer_head * buf) {
	if (!buf)
		return;
	wait_on_buffer(buf);
	lock_op(&brelse_semaphore);
	if (!(buf->b_count--))
		panic("Trying to free free buffer");
	unlock_op(&brelse_semaphore);
	wake_up(&buffer_wait);
}

/*
 * bread() reads a specified block and returns the buffer that contains
 * it. It returns NULL if the block was unreadable.
 */
struct buffer_head * bread(int dev, int block) {
	struct buffer_head * bh;

	if (!(bh = getblk(dev, block)))
		panic("bread: getblk returned NULL\n");
	if (bh->b_uptodate)
		return bh;
	ll_rw_block(READ, bh);
	wait_on_buffer(bh);

	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	printk("Can not get free bufferhead,b_uptodate: %d  \n\r", bh->b_uptodate);
	return NULL;
}

#define COPYBLK(from,to) \
__asm__("cld\n\t" \
	"rep\n\t" \
	"movsl\n\t" \
	::"c" (BLOCK_SIZE/4),"S" (from),"D" (to))

/*
 * bread_page reads four buffers into memory at the desired address. It's
 * a function of its own, as there is some speed to be got by reading them
 * all at the same time, not waiting for one to be read, and then another
 * etc.
 * 注意：这里的address是物理地址
 */
void bread_page(unsigned long address, int dev, int b[4]) {
	unsigned long* phy_addr = (unsigned long*)address;              /* 这里的address就是4K对齐的，所以不需要align */
	unsigned long linear_addr = check_remap_linear_addr(&phy_addr); /* 注意：这里的phy_addr有可能是内核空间保留的线性地址，因为当address超出内核的寻址空间会被remap为linear */
	unsigned long remaped_address = (unsigned long)phy_addr;
	struct buffer_head * bh[4];
	int i;

	for (i = 0; i < 4; i++) {
		if (b[i]) {
			if ((bh[i] = getblk(dev, b[i]))) {
				if (!bh[i]->b_uptodate) {
					ll_rw_block(READ, bh[i]);
				}
			}
		} else {
			bh[i] = NULL;
		}
	}
	for (i = 0; i < 4; i++) {
		if (bh[i]) {
			wait_on_buffer(bh[i]);
			if (bh[i]->b_uptodate) {
				COPYBLK((unsigned long) bh[i]->b_data, remaped_address);  /* 这里又是个巨坑，都怪自己大意了，千万不能(unsigned long) phy_addr这么赋值啊，这样EDI就会重置了。 */
			}
			brelse(bh[i]);
		}
	}
	recov_swap_linear(linear_addr);
}

/*
 * Ok, breada can be used as bread, but additionally to mark other
 * blocks for reading as well. End the argument list with a negative
 * number.
 */
struct buffer_head * breada(int dev, int first, ...) {
	va_list args;
	struct buffer_head * bh, *tmp;

	va_start(args, first);
	if (!(bh = getblk(dev, first)))
		panic("bread: getblk returned NULL\n");
	if (!bh->b_uptodate)
		ll_rw_block(READ, bh);
	while ((first = va_arg(args, int)) >= 0) {
		tmp = getblk(dev, first);
		if (tmp) {
			if (!tmp->b_uptodate)
				ll_rw_block(READA, bh);
			tmp->b_count--;
		}
	}
	va_end(args);
	wait_on_buffer(bh);
	if (bh->b_uptodate)
		return bh;
	brelse(bh);
	return (NULL);
}

void buffer_init(long buffer_end) {
	struct buffer_head * h = start_buffer;
	void * b;
	int i;

	if (buffer_end*0x1000 == 1 << 20)
		b = (void *) (640 * 1024);
	else
		b = (void *) (buffer_end*0x1000);
	while ((b -= BLOCK_SIZE) >= ((void *) (h + 1))) {
		h->b_dev = 0;
		h->b_dirt = 0;
		h->b_count = 0;
		h->b_lock = 0;
		h->b_uptodate = 0;
		h->b_wait = NULL;
		h->b_next = NULL;
		h->b_prev = NULL;
		h->b_data = (char *) b;
		h->b_prev_free = h - 1;
		h->b_next_free = h + 1;
		h++;
		NR_BUFFERS++;
		if (b == (void *) 0x100000)
			b = (void *) 0xA0000;
	}
	h--;
	free_list = start_buffer;
	free_list->b_prev_free = h;
	h->b_next_free = free_list;
	for (i = 0; i < NR_HASH; i++)
		hash_table[i] = NULL;
}
